﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Compilex.Automata;
using Compilex.BaseLibrary;
using Infraestruturex.BaseLibrary;
using Infraestruturex.Automata;
using Infraestruturex;
using Infraestruturex.Automata.SPA;

namespace Metex
{
    public class RuleCompiler
    {
        public string SubmachineName { get; set; }
        private Lexex lexer;
        private Dictionary<int, NFAState> states = new Dictionary<int, NFAState>();
        private Stack<Pair<int, int>> stack = new Stack<Pair<int, int>>();

        private int ns, cs;

        public RuleCompiler(string input)
        {
            lexer = new Lexex(input);
            SubmachineName = ((NonTerminalToken)lexer.GetNextToken()).Lexeme;
            WirthSymbolToken equalSign = (WirthSymbolToken)lexer.GetNextToken();
            if (equalSign.Lexeme != "=") throw new ArgumentException("Esperava = após " + SubmachineName);
        }

        public SPA CompileToSPA()
        {
            DFA dfa = CompileToDFA();
            SPA spa = new SPAfromDFABuilder(dfa).BuildSPA(SubmachineName);
            spa.OrganizeStateNames();
            return spa;
        }

        public DFA CompileToDFA()
        {
            DFA dfa = new NFAConverter(CompileToNFA()).ConvertToDFA();
            DFA minimalDFA = new DFAMinimizer(dfa).Minimize();
            return minimalDFA;
        }

        public NFA CompileToNFA()
        {
            cs = 0; ns = 1;
            CreateEmptyState(0);
            CreateFinalState(1);

            stack.Push(new Pair<int, int>(cs, ns));
            IncrementNS();

            bool foundEndDot = false;

            while (!foundEndDot && lexer.HasNextToken())
            {
                IToken token = lexer.GetNextToken();
                switch (token.Class)
                {
                    case "Terminal":
                        AddTerminalTransition(cs, ns, token.Lexeme);
                        cs = ns; IncrementNS();
                        break;
                    case "NonTerminal":
                        AddNonTerminalTransition(cs, ns, token.Lexeme);
                        cs = ns; IncrementNS();
                        break;
                    case "WirthSymbol":
                        switch (token.Lexeme)
                        {
                            case "|":
                                var top = stack.Peek();
                                AddEpsilonTransition(cs, top.Second);
                                cs = top.First;
                                break;
                            case "(":
                                stack.Push(new Pair<int, int>(cs, ns));
                                IncrementNS();
                                break;
                            case "[":
                                stack.Push(new Pair<int, int>(cs, ns));
                                AddEpsilonTransition(cs, ns);
                                IncrementNS();
                                break;
                            case "{":
                                stack.Push(new Pair<int, int>(ns, ns));
                                AddEpsilonTransition(cs, ns);
                                cs = ns;
                                IncrementNS();
                                break;
                            case ")":
                            case "]":
                            case "}":
                                top = stack.Pop();
                                AddEpsilonTransition(cs, top.Second);
                                cs = top.Second;
                                break;
                            case ".":
                                top = stack.Pop();
                                AddEpsilonTransition(cs, top.Second);
                                foundEndDot = true;
                            break;
                        }
                        break;
                    default:
                        throw new ArgumentException("Token desconhecido: " + token.Class);
                }
            }

            if (!foundEndDot)
            {
                throw new ArgumentException("Toda regra de Wirth precisa terminar com um ponto.");
            }
            if (stack.Count > 0)
            {
                throw new ArgumentException("Ponto final não esperado em " + SubmachineName);
            }

            return new NFA(states[0]);
        }

        private void AddEpsilonTransition(int from, int to)
        {
            ThrowHelper.ThrowIfFalse(states.ContainsKey(from), "O estado de origem " + from + " não existe.");
            ThrowHelper.ThrowIfFalse(states.ContainsKey(to), "O estado de destino " + to + " não existe.");
            states[from].AddEpsilonTransition(states[to]);
        }

        private void IncrementNS()
        {
            ns++;
            states[ns] = new NFAState("q"+ns);
        }

        private void AddTerminalTransition(int from, int to, string lexeme)
        {
            ThrowHelper.ThrowIfFalse(states.ContainsKey(from), "O estado de origem " + from + " não existe.");
            ThrowHelper.ThrowIfFalse(states.ContainsKey(to), "O estado de destino " + to + " não existe.");
            // remove as aspas
            states[from].AddTransition(lexeme.Substring(0, lexeme.Length - 1).Substring(1), states[to]);
        }

        private void AddNonTerminalTransition(int from, int returnState, string submachineName)
        {
            ThrowHelper.ThrowIfFalse(states.ContainsKey(from), "O estado de origem " + from + " não existe.");
            ThrowHelper.ThrowIfFalse(states.ContainsKey(returnState), "O estado de destino " + returnState + " não existe.");
            states[from].AddTransition(new StubSubmachineTransition<NFAState>(states[returnState], submachineName));
        }

        private void CreateFinalState(int n)
        {
            states[n] = new NFAState("q" + n, true);
        }

        private void CreateEmptyState(int n)
        {
            states[n] = new NFAState("q" + n);
        }
    }
}
